package com.xyy.saas.payment.merchant.core.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xyy.saas.payment.adpater.RouteManager;
import com.xyy.saas.payment.adpater.impl.PinganAdapter;
import com.xyy.saas.payment.callback.core.dto.CallbackMsg;
import com.xyy.saas.payment.callback.core.result.CallbackRunner;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.cores.bo.UserBo;
import com.xyy.saas.payment.cores.enums.ApplyBankcardStatusEnum;
import com.xyy.saas.payment.cores.enums.BalanceAccountTypeEnum;
import com.xyy.saas.payment.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.cores.vo.CallbackResultVo;
import com.xyy.saas.payment.dao.model.ApplyBankcardModify;
import com.xyy.saas.payment.dao.model.ThirdUserInfo;
import com.xyy.saas.payment.merchant.core.dto.*;
import com.xyy.saas.payment.merchant.core.vo.PaymentAuthQueryVo;
import com.xyy.saas.payment.merchant.core.vo.UserInfoVo;
import com.xyy.saas.payment.service.ApplyBankcardModifyService;
import com.xyy.saas.payment.service.PayAccountService;
import com.xyy.saas.payment.service.ThirdUserInfoService;
import com.xyy.saas.payment.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;

@Service
@Slf4j
public class ApplyBankcardService {
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private ApplyBankcardService applyBankcardService;
    @Resource
    private ApplyBankcardModifyService applyBankcardModifyService;
    @Resource
    private LeafUtil leafUtil;
    @Resource
    private PinganAdapter pinganAdapter;
    @Resource
    private RouteManager routeManager;
    @Resource
    private CallbackRunner callbackRunner;
    @Resource
    private PayAccountService payAccountService;
    @Resource
    private UserInfoService2 userInfoService2;
    @Resource
    private FBank2PinganMoveService fBank2PinganMoveService;
    @Resource
    private ThirdUserInfoService thirdUserInfoService;

    @Value("${unCheckPingAnSummarySwitch:false}")
    private Boolean unCheckPingAnSummarySwitch;
    @Value("#{'${unCheckPingAnSummaryAccountIds:}'.split(',')}")
    private List<String> unCheckPingAnSummaryAccountIds;

    /**
     * 查询绑卡状态:true-已绑定，false-未绑定
     */
    public boolean isBinded(String userId,String account) throws PaymentException {
        List<MerchantCardBindDto> merchantCardBindDtos = pinganAdapter.queryBindCardState(userId);
        if (CollectionUtils.isEmpty(merchantCardBindDtos)) {
            return false;
        }
        final long count = merchantCardBindDtos.stream()
                .filter(x -> StringUtils.isNotEmpty(x.getAccount()) && x.getAccount().equals(account)).count();
        if (count > 0) {
            return true;
        }
        return false;
    }

    /**
     * 申请绑卡
     */
    public Integer apply(ApplyBankcardModifyDto param, boolean unBind) throws PaymentException {
        log.info("ApplyBankcardModifyService#apply param={}, unBind={}", JSONUtils.toJSON(param), unBind);

        // 0.生成申请单号
        if(StringUtils.isBlank(param.getApplyNo())) {
            param.setApplyNo(leafUtil.getSnowflakeId());
        }

        // 1.参数校验
        param.checkParam();
        ApplyBankcardStatusEnum status = ApplyBankcardStatusEnum.AUDITING;

        // 2.若为重发短信，则验证是否存在待验证绑卡数据
        ApplyBankcardModify applyBankcardModify = repeatCheck(param.getAccountId());
        if (null != param.getResendCode() && param.getResendCode() == 1) {
            if (null == applyBankcardModify || !ApplyBankcardStatusEnum.PASS.getStatus().equals(applyBankcardModify.getStatus())) {
                throw new PaymentException("不存在待验证绑卡数据！");
            }
            status = ApplyBankcardStatusEnum.PASS;
        }
        // 3.若不为重发短信，重复提交校验检查
        else {
            if (null != applyBankcardModify && !ApplyBankcardStatusEnum.canCreate(applyBankcardModify.getStatus())) {
                throw new PaymentException(ResultCodeEnum.ACCOUNT_MODIFY_AUDITING);
            } else {
                applyBankcardModify = param.transfer();
                applyBankcardModifyService.insertSelective(applyBankcardModify);
            }
        }

        PaymentException exception = null;
        JSONObject extendInfo = new JSONObject();
        // 4.绑卡申请
        try {
            if (unBind) {
                log.info("ApplyBankcardModifyService#apply 解绑判断 param = {}", JSON.toJSONString(param));
                boolean isBinded = applyBankcardService.isBinded(param.getAccountId(), param.getAcct());
                if (isBinded) {
                    log.info("ApplyBankcardModifyService#apply 卡已绑定 开始走解绑逻辑:UserId:{},accout:{}", param.getAccountId(), param.getAcct());
                    UnBindCardDto unBindCardDto = new UnBindCardDto();
                    unBindCardDto.setAccount(param.getAcct());
                    unBindCardDto.setAccountName(param.getAccountName());
                    unBindCardDto.setMobile(param.getMobileNo());
                    unBindCardDto.setUserId(param.getAccountId());
                    unBindCardDto.setAccountType(Constants.MERCHANT);
                    unBindCardDto.setBusinessIdType(param.getBusinessIdType().getType());
                    routeManager.unBindCard(unBindCardDto);
                }
            }

            String applyNo = routeManager.merchantCardBind(
                    MerchantCardBindDto.builder().
                    businessOrderNo(param.getBusinessOrderNo()).
                    businessIdType(param.getBusinessIdType().getType()).
                    businessUserId(param.getBusinessId()).
                    userId(param.getAccountId()).
                    applyNo(param.getApplyNo()).
                    account(param.getAcct()).
                    settleName(param.getAccountName()).
                    openBank(param.getBankName()).
                    branchBankCd(param.getBranchBankCd()).
                    corporationName(param.getCorporationName()).
                    corporationIdNo(param.getCorporationIdNo()).
                    corporationPhone(param.getCorporationPhone()).
                    contactName(param.getContactName()).
                    contactIdCard(param.getContactIdCard()).
                    contactPhone(param.getContactPhone()).
                    build()
            );
            extendInfo.put("ReservedMsg", applyNo);
            // 若为重发短信，则不进行更新状态，仅处理数据返回
            if (null != param.getResendCode() && param.getResendCode() == 1) {
                log.info("重发短信，不进行处理，param = {}", JSON.toJSONString(param));
            } else {
                if (StringUtils.isNotBlank(applyNo) && !"VERIFIED".equals(applyNo)) {
                    // 通过applyNo查询原流水
                    ApplyBankcardModify infoByApplyNo = applyBankcardModifyService.queryApplyInfoByApplyNo(applyNo);
                    // 若原流水存在，则更新原流水申请单号
                    if (null != infoByApplyNo) {
                        infoByApplyNo.setApplyNo(infoByApplyNo.getApplyNo() + "_1");
                        extendInfo.put("paymentDate", infoByApplyNo.getCreateTime());
                        applyBankcardModify.setCreateTime(infoByApplyNo.getCreateTime());
                        applyBankcardModifyService.updateByPrimary(infoByApplyNo);
                    }
                    // 设置流水号
                    applyBankcardModify.setApplyNo(applyNo);
                }
            }
        }
        catch(PaymentException ex) {
            log.error("ApplyBankcardService#apply error. message=" + ex.getMessage() + ", param=" + JSONUtils.toJSON(param), ex);
            status = ApplyBankcardStatusEnum.FAIL;
            exception = ex;
        }

        // 若为重发短信，则不进行更新状态，仅处理数据返回
        if (null != param.getResendCode() && param.getResendCode() == 1) {
            if(exception != null) {
                throw exception;
            }
            return ApplyBankcardStatusEnum.PASS.getStatus();
        }

        // 5.变更状态
        applyBankcardModify.setStatus(status.getStatus());
        applyBankcardModify.setMessage(exception != null ? exception.getErrMsg() : status.getDesc());
        applyBankcardModify.setExtendInfo(extendInfo.toJSONString());
        applyBankcardModifyService.updateByPrimary(applyBankcardModify);

        // 6.抛出异常
        if(exception != null) {
            throw exception;
        }

        return status.getStatus();
    }

    /**
     * 同步申请状态
     */
    public void syncApplyStatus() {
        log.info("ApplyBankcardModifyService#syncApplyStatus");
        List<ApplyBankcardModify> items = applyBankcardModifyService.queryApplyInfoStatus(ApplyBankcardStatusEnum.AUDITING.getStatus());
        log.info("ApplyBankcardModifyService#syncApplyStatus size = {}", items.size());

        if(CollectionUtils.isEmpty(items)) {
            return;
        }

        items.forEach(item -> {
            try {
                // 1.查询状态
                PaymentAuthQueryVo result = routeManager.paymentAuthQuery(PaymentAuthQueryDto.builder().
                        businessIdType(item.getBusinessIdType()).
                        accountId(item.getAccountId()).
                        applyNo(item.getApplyNo()).
                        applyDate(DateUtil.format(item.getCreateTime(), DateUtil.DATEFORMATDAY_02)).
                        build());

                if(!PaymentAuthQueryVo.UNKNOWN.equals(result.getStatus())) {
                    ApplyBankcardStatusEnum status = result.isSuccess() ? ApplyBankcardStatusEnum.PASS : ApplyBankcardStatusEnum.REJECT;
                    String message = result.isSuccess() ? ApplyBankcardStatusEnum.PASS.getDesc() : result.getMessage();
                    String type = result.isSuccess() ? CallbackResultVo.BANK_CARD_AUDIT_PASS : CallbackResultVo.BANK_CARD_AUDIT_FAIL;

                    // 2.更新本地状态
                    item.setStatus(status.getStatus());
                    item.setMessage(message);
                    applyBankcardModifyService.updateByPrimary(item);

                    // 3.通知业务
                    String businessId = payAccountService.getBusinessUserIdByAccountId(item.getAccountId());

                    // 兼容换签
                    if(businessId.contains(AccountDto.SUFFIX_SIGN_CHANGE)) {
                        businessId = businessId.substring(NumberUtils.INTEGER_ZERO, businessId.indexOf(AccountDto.SUFFIX_SIGN_CHANGE));
                    }

                    callbackRunner.callback(CallbackMsg.build(type, item.getCallback(), businessId, item.getBusinessOrderNo(), message));
                }
            }
            catch(Exception ex) {
                log.error("查询打款结果异常：{}", JSONUtils.toJSON(item), ex);

                // 更新本地状态
                item.setStatus(ApplyBankcardStatusEnum.FAIL.getStatus());
                item.setMessage(ex.getMessage());
                applyBankcardModifyService.updateByPrimary(item);

                // 通知业务
                String businessId = payAccountService.getBusinessUserIdByAccountId(item.getAccountId());
                // 兼容换签
                if(businessId.contains(AccountDto.SUFFIX_SIGN_CHANGE)) {
                    businessId = businessId.substring(NumberUtils.INTEGER_ZERO, businessId.indexOf(AccountDto.SUFFIX_SIGN_CHANGE));
                }
                callbackRunner.callback(CallbackMsg.build(CallbackResultVo.BANK_CARD_AUDIT_FAIL, item.getCallback(), businessId, item.getBusinessOrderNo(), ex.getMessage()));
            }
        });
    }

    /**
     * 打款认证
     */
    public void auth(PaymentAuthParam2 param) throws Exception {
        log.info("ApplyBankcardModifyService#auth param2={}", JSONUtils.toJSON(param));

        // 1.参数校验
        param.checkParam();

        String accountId = payAccountService.getAccountIdByBusiness(param.getBusinessIdType().getType(), param.getBusinessId());

        if(StringUtils.isBlank(accountId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }

        // 结算期间不允许打款认证
        if(redisUtil.exists(RedisUtil.D_AMOUNT_LOCK + accountId)) {
            throw new PaymentException(ResultCodeEnum.PROFIT_SHARING_PAYMENTAUTH_IS_NOT_ALLOWED);
        }

        // 2.校验申请单状态校验
        ApplyBankcardModify applyBankcardModify = applyBankcardModifyService.queryApplyInfoByAccountId(accountId, StringUtils.EMPTY);
        // 是否需要迁移数据
        Boolean needMove = Boolean.FALSE;

        if(applyBankcardModify == null) {
            // 兼容换签
            String signChangeAccountId = payAccountService.getAccountIdByBusiness(param.getBusinessIdType().getType(), param.getBusinessId() + AccountDto.SUFFIX_SIGN_CHANGE);

            if(StringUtils.isNotBlank(signChangeAccountId)) {
                applyBankcardModify = applyBankcardModifyService.queryApplyInfoByAccountId(signChangeAccountId, StringUtils.EMPTY);
                needMove = Boolean.TRUE;

                boolean unCheckPingAnSummary = Boolean.TRUE.equals(unCheckPingAnSummarySwitch) &&
                        !CollectionUtils.isEmpty(unCheckPingAnSummaryAccountIds) &&
                        unCheckPingAnSummaryAccountIds.contains(signChangeAccountId);
                if (unCheckPingAnSummary) {

                } else {
                    // 平安挂账金额校验
                    checkPingAnSummaryAccount(param.getBusinessIdType().getType(), accountId);
                }

            }

            if(applyBankcardModify == null) {
                throw new PaymentException(ResultCodeEnum.BANK_CARD_APPLY_NOT_FOUNT);
            }
        }

        // 幂等判断
        if(ApplyBankcardStatusEnum.COMPLETE.getStatus().equals(applyBankcardModify.getStatus())) {
            log.info("ApplyBankcardModifyService#auth idempotent param2={}", JSONUtils.toJSON(param));
            return;
        }

        if(!ApplyBankcardStatusEnum.PASS.getStatus().equals(applyBankcardModify.getStatus())) {
            throw new PaymentException(ResultCodeEnum.BANK_CARD_APPLY_STATUS_NOT_PASS);
        }

        ApplyBankcardStatusEnum status = null;
        String message = null;
        Exception exception = null;
        ApplyBankcardModifyDto applyBankcardModifyDto = null;

        try {
            applyBankcardModifyDto = ApplyBankcardModifyDto.transfer(applyBankcardModify);

            // 3.打款认证
            routeManager.paymentAuth(PaymentAuthDto.builder().
                    businessIdType(applyBankcardModify.getBusinessIdType()).
                    accountId(applyBankcardModify.getAccountId()).
                    accountNo(applyBankcardModifyDto.getAcct()).
                    amount(param.getAmount()).
                    orderNo(param.getOrderNo()).
                    build());

            // 4.同步信息
            if(Constants.SOURCE_MODIFY_BANKCARD.equals(applyBankcardModifyDto.getSource()) ||
                    Constants.SOURCE_OPEN_ACCOUNT.equals(applyBankcardModifyDto.getSource()))
            {
                UserInfoVo userInfoVo = userInfoService2.queryUserInfo(applyBankcardModifyDto.getAccountId());
                applyBankcardModifyDto.copy2UserInfo(userInfoVo);
                userInfoService2.updateUserInfo(userInfoVo);
            }

            // 5.设置状态
            status = ApplyBankcardStatusEnum.COMPLETE;
            message = ApplyBankcardStatusEnum.COMPLETE.getDesc();
        }
        catch(Exception ex) {
            log.error(ex.getMessage() + ", 打款认证异常, param=" + JSONUtils.toJSON(param), ex);

            if(ex instanceof PaymentException) {
                String errCode = ((PaymentException) ex).getErrCode();

                /**
                 * 特殊code返回审核失败，让用户重提
                 * ERR146 该提现账号已经绑定到了其他子账号上
                 * ERR147 无有效的待验证小额转账鉴权记录
                 * E11003 原绑定提现账号的鉴权申请已经失败，请重新发起
                 * E11002 申请的短信指令号已经失效
                 * ERR257 该次鉴权转账失败，请重新发起鉴权申请
                 * ERR258 验证超过限定次数，请重新发起鉴权
                 */
                if("ERR146".equals(errCode) || "ERR147".equals(errCode) || "E11003".equals(errCode) || "E11002".equals(errCode) ||
                        "ERR257".equals(errCode) || "ERR258".equals(errCode))
                {
                    exception = new PaymentException(ResultCodeEnum.BANK_CARD_AUDIT_FAIL, ((PaymentException) ex).getErrMsg());
                    status = ApplyBankcardStatusEnum.FAIL;
                    message = ((PaymentException) ex).getErrMsg();
                }
                /**
                 * ERR238 该会员已经绑定此账号，请勿重复绑定
                 */
                else if("ERR238".equals(errCode)) {
                    // 同步信息
                    if(applyBankcardModifyDto != null && Constants.SOURCE_MODIFY_BANKCARD.equals(applyBankcardModifyDto.getSource())) {
                        UserInfoVo userInfoVo = userInfoService2.queryUserInfo(applyBankcardModifyDto.getAccountId());
                        applyBankcardModifyDto.copy2UserInfo(userInfoVo);
                        userInfoService2.updateUserInfo(userInfoVo);
                    }

                    // 设置状态
                    status = ApplyBankcardStatusEnum.COMPLETE;
                    message = ApplyBankcardStatusEnum.COMPLETE.getDesc();
                }
                else {
                    exception = ex;
                }
            }
            else {
                exception = ex;
            }
        }

        // 4.更新申请单状态
        if(status != null) {
            applyBankcardModify.setStatus(status.getStatus());
            applyBankcardModify.setMessage(message);
            applyBankcardModifyService.updateByPrimary(applyBankcardModify);
        }

        // 5.抛异常
        if(exception != null) {
            throw exception;
        }

        // 6.迁移数据
        if(needMove) {
            fBank2PinganMoveService.move(param.getBusinessIdType().getType(),
                    param.getBusinessId(), param.getBusinessId() + AccountDto.SUFFIX_SIGN_CHANGE);
        }
    }

    /**
     * 校验：一个商户同时只能有一个进行中单据
     */
    private ApplyBankcardModify repeatCheck(String accountId) throws PaymentException {
        // 5s内重复提交
        if(StringUtils.isNotBlank(redisUtil.get(RedisUtil.APPLY_BANKCARD_PREFIX + accountId))) {
            throw new PaymentException(ResultCodeEnum.ACCOUNT_MODIFY_PROCESSING);
        }

        redisUtil.set(RedisUtil.APPLY_BANKCARD_PREFIX + accountId, RedisUtil.SECOND_5);

        // 同一商户串行校验
        ApplyBankcardModify item = applyBankcardModifyService.queryApplyInfoByAccountId(accountId, StringUtils.EMPTY);

        // 若为创建中，或者审核中，则不允许重新申请
        if(item != null && !ApplyBankcardStatusEnum.canApply(item.getStatus())) {
            throw new PaymentException(ResultCodeEnum.ACCOUNT_MODIFY_AUDITING);
        }

        return item;
    }

    /**
     * 换签余额校验
     */
    private void checkPingAnSummaryAccount(String businessIdType, String fmAccountId) throws PaymentException {
        ThirdUserInfo fBankThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(fmAccountId);

        if(fBankThirdUserInfo == null)  {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        UserBo fBankBalance = routeManager.queryAccount(QueryAccountDto.
                build(businessIdType, fmAccountId, fBankThirdUserInfo.getThirdUserId()));

        if(fBankBalance.getWithdrawableBalance() <= NumberUtils.INTEGER_ZERO) {
            return;
        }

        UserBo pingAnSummaryBalance = routeManager.queryAccount(QueryAccountDto.
                build(businessIdType, ApolloUtil.getProperty(Constants.EC_PINGAN_SUMMARY_ACCOUNT_ID),
                        ApolloUtil.getProperty(Constants.EC_PINGAN_SUMMARY_ACCOUNT_ID),
                        BalanceAccountTypeEnum.FUNCTION_ACCOUNT.getType()));

        log.info("ApplyBankcardService#checkPingAnSummaryAccount fBankBalance={}, pingAnSummaryBalance={}",
                JSONUtils.toJSON(fBankBalance), JSONUtils.toJSON(pingAnSummaryBalance));

        if(fBankBalance.getWithdrawableBalance() > pingAnSummaryBalance.getAvailableBalance()) {
            throw new PaymentException(ResultCodeEnum.SUMMARY_ACCOUNT_BALANCE_NOT_ENOUGH);
        }
    }

    public void mntMbrBindSameRealCustNameAcct(String thirdUserId, String sellerThirdUserId) throws PaymentException {
        pinganAdapter.mntMbrBindSameRealCustNameAcct(thirdUserId,sellerThirdUserId);
    }
}
